package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.bean.BeanUtils;
import com.spzx.product.api.domain.Product;
import com.spzx.product.api.domain.ProductDetails;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.vo.SkuLockVo;
import com.spzx.product.api.domain.vo.SkuPrice;
import com.spzx.product.api.domain.vo.SkuQuery;
import com.spzx.product.api.domain.vo.SkuStockVo;
import com.spzx.product.domain.SkuStock;
import com.spzx.product.mapper.ProductDetailsMapper;
import com.spzx.product.mapper.ProductMapper;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.mapper.SkuStockMapper;
import com.spzx.product.service.IProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 商品Service业务层处理
 */
@Slf4j
@Service
@Transactional
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private ProductSkuMapper productSkuMapper;

    @Autowired
    private ProductDetailsMapper productDetailsMapper;

    @Autowired
    private SkuStockMapper skuStockMapper;

    //@Autowired
    //private StringRedisTemplate stringRedisTemplate; //适合 key和value都是字符串类型

    @Autowired
    private RedisTemplate redisTemplate; //适合值是任意类型

    /**
     * 查询商品列表
     *
     * @param product 商品
     * @return 商品
     */
    @Override
    public List<Product> selectProductList(Product product) {
        return productMapper.selectProductList(product);
    }

    //原子性
    @Override
    public int insertProduct(Product product) {
        //1.保存Product对象到product表
        productMapper.insert(product); //主键回填

        //2.保存List<ProductSku>对象到product_sku表
        List<ProductSku> productSkuList = product.getProductSkuList();
        if (CollectionUtils.isEmpty(productSkuList)) {
            throw new ServiceException("SKU数据为空");
        }
        int size = productSkuList.size();
        for (int i = 0; i < size; i++) {
            ProductSku productSku = productSkuList.get(i);
            productSku.setSkuCode(product.getId() + "_" + i);
            productSku.setSkuName(product.getName() + " " + productSku.getSkuSpec());
            productSku.setProductId(product.getId());
            productSkuMapper.insert(productSku);

            //添加商品库存  //3.保存List<SkuStock>对象到sku_stock表
            SkuStock skuStock = new SkuStock();
            skuStock.setSkuId(productSku.getId());
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setLockNum(0);
            skuStock.setAvailableNum(productSku.getStockNum());
            skuStock.setSaleNum(0);
            skuStockMapper.insert(skuStock);
        }

        //4.保存ProductDetails对象到product_details表
        ProductDetails productDetails = new ProductDetails();
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetails.setProductId(product.getId());
        productDetailsMapper.insert(productDetails);

        return 1;
    }


    @Override
    public Product selectProductById(Long id) {
        //1.根据id查询Product对象
        Product product = productMapper.selectById(id);

        //2.封装扩展字段：查询商品对应多个List<ProductSku>
        //select * from product_sku where product_id =?
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, id));
        List<Long> productSkuIdList = productSkuList.stream().map(productSku -> productSku.getId()).toList();


        // select * from sku_stock where sku_id in (1,2,3,4,5,6)
        List<SkuStock> skuStockList = skuStockMapper.selectList(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId, productSkuIdList));

        Map<Long, Integer> skuIdToTatalNumMap = skuStockList.stream().collect(Collectors.toMap(SkuStock::getSkuId, SkuStock::getTotalNum));
        productSkuList.forEach(productSku -> {
            //返回ProductSku对象，携带了库存数据；
            productSku.setStockNum(skuIdToTatalNumMap.get(productSku.getId()));
        });

        product.setProductSkuList(productSkuList);

        //3.封装扩展字段：商品详情图片List<String>
        ProductDetails productDetails = productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, id));
        String imageUrls = productDetails.getImageUrls();   //url,url,url
        String[] urls = imageUrls.split(",");
        product.setDetailsImageUrlList(Arrays.asList(urls));
        //返回Product对象
        return product;
    }


    @Override
    public int updateProduct(Product product) {
        //1.更新Product
        productMapper.updateById(product);

        //2.更新SKU   List<ProductSku>
        List<ProductSku> productSkuList = product.getProductSkuList();
        if (CollectionUtils.isEmpty(productSkuList)) {
            throw new ServiceException("SKU数据为空");
        }
        productSkuList.forEach(productSku -> {
            productSkuMapper.updateById(productSku);

            //3.更新库存   List<ProductSku> -> 获取扩展字段stockNum
            SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, productSku.getId()));
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setAvailableNum(skuStock.getTotalNum() - skuStock.getLockNum());
            skuStockMapper.updateById(skuStock);
        });

        //4.更新详情ProductDetails
        ProductDetails productDetails = productDetailsMapper
                .selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, product.getId()));
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetailsMapper.updateById(productDetails);

        return 1;
    }


    @Override
    public int deleteProductByIds(Long[] ids) {
        //1.删除Product表数据
        // delete from product where id in (1,2)
        productMapper.deleteBatchIds(Arrays.asList(ids));

        //2.删除ProductSku表数据
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getProductId, Arrays.asList(ids)));
        List<Long> productSkuIdList = productSkuList.stream().map(ProductSku::getId).toList();
        productSkuMapper.deleteBatchIds(productSkuIdList);

        //3.删除SkuStock表数据
        skuStockMapper.delete(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId, productSkuIdList));

        //4.删除ProductDetails表数据
        // delete from product_details where product_id in (1,2)
        productDetailsMapper.delete(new LambdaQueryWrapper<ProductDetails>().in(ProductDetails::getProductId, Arrays.asList(ids)));
        return 1;
    }


    @Override
    public void updateAuditStatus(Long id, Integer auditStatus) {
        Product product = new Product();
        product.setId(id);
        if (auditStatus == 1) {
            product.setAuditStatus(1);
            product.setAuditMessage("审批通过");
        } else {
            product.setAuditStatus(-1);
            product.setAuditMessage("审批拒绝");
        }
        productMapper.updateById(product);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateStatus(Long id, Integer status) {
        String dataKey = "sku:product:data"; // bitmap数据的key
        Product product = new Product();
        product.setId(id);
        if (status == 1) {
            product.setStatus(1);
        List<ProductSku> productSkuList=productSkuMapper.selectList(
                new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId,id)
        );
            for (ProductSku productSku : productSkuList) {
                redisTemplate.opsForValue().setBit(dataKey,productSku.getId(),true);
            }

        } else {
            product.setStatus(-1); //下架
            List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, id));
            for (ProductSku productSku : productSkuList) {
                redisTemplate.opsForValue().setBit(dataKey, productSku.getId(), false);
            }
        }
        productMapper.updateById(product);
    }


    @Override
    public List<ProductSku> getTopSale() {
        return productSkuMapper.getTopSale();
    }


    @Override
    public List<ProductSku> skuList(SkuQuery skuQuery) {
        return productSkuMapper.skuList(skuQuery);
    }


    /**
     * 服务提供者：6个接口来服务于商品详情查询。需要进行优化，提供查询效率。
     * 需要使用redis来提高性能。
     */

    @Override
    public ProductSku getProductSku(Long skuId) {

        try {//用于兜底，发生异常直接去数据库查询
            //1.优先从缓存中获取数据
            //1.1 构建业务数据Key 形式：前缀+业务唯一标识
            String datakey="product:sku:"+skuId;
            //1.2 查询Redis获取业务数据
            ProductSku productSku= (ProductSku) redisTemplate.opsForValue().get(datakey);//opsForValue用于操作字符串
            //1.3 命中缓存则直接返回
            if (redisTemplate.hasKey(datakey)){//检查 Redis 中是否存在指定的键（key）
                log.info("命中缓存，直接返回，线程ID：{}，线程名称：{}",Thread.currentThread().getId(),
                        Thread.currentThread().getName());
                return productSku;
            }
            //2.尝试获取分布式锁（set k v ex nx可能获取锁失败）
            //2.1 构建锁key
            String lockKey="product:sku:lock:"+skuId;
            //2.2 采用UUID作为线程标识
            String lockVal = UUID.randomUUID().toString().replaceAll("-", "");
            //2.3 利用Redis提供set nx ex 获取分布式锁
            //setIfAbsent 的原子性操作 可以确保在缓存失效时，只有一个线程能够执行重建缓存的操作，其他线程则等待缓存重建完成后再从缓存读取数据。
            // 这避免了多个线程同时访问数据库，从而缓解缓存击穿的问题。
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);//setIfAbsent不存在时设置（原子性）
            if (flag){//3.获取锁成功执行业务,将查询业务数据放入缓存Redis
                try {
                    log.info("获取锁成功：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                    productSku = this.getproductSkuFromDB(skuId);//从数据库中查数据
                    long ttl=productSku==null?1*60:10*60;//如果从数据库中没有查到productSku==null空结果也进行缓存，我们设置他的过期时间为1分钟，但是不能防止随机穿透。
                    // 结果不为空设置为10分钟，建议设置不同的时间用来防止缓存雪崩
                    //这里需要设置缓存过期的不同时间，用于防止缓存雪崩，但是我们没有设置
                    redisTemplate.opsForValue().set(datakey,productSku,ttl,TimeUnit.SECONDS);
                    return productSku;
                } finally {
                    //4.业务执行完毕释放锁
                    // -- KEYS[1] 表示锁的键名（lockKey）
                    //-- ARGV[1] 表示锁的唯一标识值（lockVal）
                    String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +  //检查当前锁的值是否等于传入的值
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +                 //如果是则删除锁（返回1表示成功）
                            "else\n" +
                            "    return 0\n" +                                           //否则返回0（不执行删除）
                            "end";
                    //Redis 的 Lua 脚本能保证 原子性
                    DefaultRedisScript<Long> redisScript=new DefaultRedisScript<>();//Spring 封装的 Redis 脚本执行对象
                    redisScript.setScriptText(scriptText);
                    redisScript.setResultType(Long.class);
                    redisTemplate.execute(redisScript,Arrays.asList(lockKey),lockVal);
                    //lockVal传递给 Lua 脚本的参数（ARGV[1]），即当前线程持有的锁的值（例如 uuid 或线程标识）
                    // 唯一标识 lockVal：每个线程在加锁时会生成一个唯一的标识（如 UUID），释放锁时必须传入相同的值,防止误删其他线程的锁
                    //execute方法用于执行 Redis 命令或 Lua 脚本, Lua 脚本对象，定义了释放锁的逻辑（例如：检查锁的持有者并删除锁）
                }
            } else {
                //5.获取锁失败则自旋（业务要求必须执行）,此时分布式锁已经被其他线程抢到了，自旋重新调用，如果其他线程从数据库查到数据存入缓存，我们自旋可以直接从缓存查到数据
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.error("获取锁失败，自旋：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                return this.getProductSku(skuId);
            }
        } catch (RuntimeException e) {
            //兜底处理方案：Redis服务有问题，将业务数据获取自动从数据库获取
            log.error("[商品服务]查询商品信息异常：{}", e);
            return this.getproductSkuFromDB(skuId);
        }

    }

    //调用方法用于从数据库中查数据
    private ProductSku getproductSkuFromDB(Long skuId) {
        return productSkuMapper.selectById(skuId);
    }


    @Override
    public Product getProduct(Long id) {
        return productMapper.selectById(id);
    }


    @Override
    public SkuPrice getSkuPrice(Long skuId) {
        ProductSku productSku = productSkuMapper.selectOne(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getId, skuId).select(ProductSku::getSalePrice, ProductSku::getMarketPrice));
        SkuPrice skuPrice = new SkuPrice();
        BeanUtils.copyProperties(productSku, skuPrice);
        return skuPrice;
    }


    @Override
    public ProductDetails getProductDetails(Long id) {
        return productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, id));
    }


    @Override
    public Map<String, Long> getSkuSpecValue(Long id) {
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, id).select(ProductSku::getId, ProductSku::getSkuSpec));
        Map<String, Long> skuSpecValueMap = new HashMap<>();
        productSkuList.forEach(item -> {
            skuSpecValueMap.put(item.getSkuSpec(), item.getId());
        });
        return skuSpecValueMap;
    }


    @Override
    public SkuStockVo getSkuStock(Long skuId) {
        SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, skuId));
        SkuStockVo skuStockVo = new SkuStockVo();
        BeanUtils.copyProperties(skuStock, skuStockVo);
        return skuStockVo;
    }


    // select * from product_sku where id in (1,2,3)
    // select id,sale_price,market_price from product_sku where id in (1,2,3)
    @Override
    public List<SkuPrice> getSkuPriceList(List<Long> skuIdList) {
        if (CollectionUtils.isEmpty(skuIdList)) {
            return new ArrayList<SkuPrice>();
        }
        List<ProductSku> skuList = productSkuMapper
                .selectList(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getId, skuIdList)
                        .select(ProductSku::getId, ProductSku::getSalePrice, ProductSku::getMarketPrice));
        if (CollectionUtils.isEmpty(skuList)) {
            return new ArrayList<SkuPrice>();
        }
        return skuList.stream().map((sku) -> {
            SkuPrice skuPrice = new SkuPrice();
            skuPrice.setSkuId(sku.getId());
            skuPrice.setSalePrice(sku.getSalePrice());
            skuPrice.setMarketPrice(sku.getMarketPrice());
            return skuPrice;
        }).toList();//toList转化为list
    }


    //检查与锁定库存
    @Transactional
    @Override
    public String checkAndLock(String orderNo, List<SkuLockVo> skuLockVoList) {
        //1.去重  (openfeign远程调用可能开启重试机制)   setnx lock 1
        String key = "sku:checkAndLock:" + orderNo; //分布式锁
        String dataKey = "sku:lock:data:" + orderNo;  //缓存锁定数据的key
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, 1, 1, TimeUnit.HOURS);
        if(!ifAbsent){
            if(redisTemplate.hasKey(dataKey)){
                return ""; //锁库存成功，没消息就是好消息
            }else{
                return "重复提交";
            }
        }

        //2.检查库存
        //SELECT * FROM `sku_stock` where sku_id=1 and 5<=available_num for update    行锁
        for (SkuLockVo skuLockVo : skuLockVoList) {
            SkuStock skuStock = skuStockMapper.check(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
            // 设置库存状态标志
            if(skuStock==null){
                skuLockVo.setIsHaveStock(false);// 库存不足，setIsHaveStock是否有库存
            }else{
                skuLockVo.setIsHaveStock(true);// 库存充足
            }
        }

        //3.锁库存，存储数据到redis中
        if(skuLockVoList.stream().anyMatch(skuLockVo->!skuLockVo.getIsHaveStock())){ //存在没有库存的情况 - 组合消息
            StringBuilder builder = new StringBuilder();
            skuLockVoList.stream().filter(skuLockVo->!skuLockVo.getIsHaveStock()).forEach(skuLockVo->{
                builder.append("商品【"+skuLockVo.getSkuId()+"】库存不足;");
            });
            if(StringUtils.hasText(builder.toString())){
                redisTemplate.delete(key); //删除分布式锁
                return builder.toString();
            }
        }else{ //库存都够 - 锁库存
            for (SkuLockVo skuLockVo : skuLockVoList) {
                //update sku_stock set lock_num=lock_num+5, available_num=available_num-5 where sku_id=1
                int lock = skuStockMapper.lock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
                if(lock==0){
                    redisTemplate.delete(key); //删除分布式锁
                    throw new ServiceException("锁库存失败");
                }
            }
        }

        //锁定库存后需要将数据保存到redis中，以便后续 解锁库存(延迟消息-没支付 or 用户手动取消)  或 减库存(支付成功(30分钟内))
        redisTemplate.opsForValue().set(dataKey, skuLockVoList); //是否 加超时时间？？？  不加

        return ""; //锁库存成功
    }




    @Transactional(rollbackFor = Exception.class)
    @Override
    public void unlock(String orderNo) {
        //1.去重  (异步消息幂等处理)
        String key = "sku:unlock:" + orderNo; //分布式锁
        String dataKey = "sku:lock:data:" + orderNo;  //缓存锁定数据的key
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, 1, 1, TimeUnit.HOURS);
        if(!ifAbsent){
            return; //重复消息直接结束了
        }

        //获取到分布式锁
        List<SkuLockVo> skuLockVoList = (List<SkuLockVo>)redisTemplate.opsForValue().get(dataKey);
        if(CollectionUtils.isEmpty(skuLockVoList)){
            return;
        }
        for (SkuLockVo skuLockVo : skuLockVoList) {
            int row = skuStockMapper.unlock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
            if(row==0){
                redisTemplate.delete(key);
                throw new ServiceException("解锁失败");
            }
        }

        //解锁后立即删除缓存。避免重复解锁库存或减库存
        redisTemplate.delete(dataKey);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void minus(String orderNo) {
        //1.去重  (异步消息幂等处理)
        String key = "sku:minus:" + orderNo; //分布式锁
        String dataKey = "sku:lock:data:" + orderNo;  //缓存锁定数据的key
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, 1, 1, TimeUnit.HOURS);
        if(!ifAbsent){
            return; //重复消息直接结束了
        }

        //获取到分布式锁
        List<SkuLockVo> skuLockVoList = (List<SkuLockVo>)redisTemplate.opsForValue().get(dataKey);
        if(CollectionUtils.isEmpty(skuLockVoList)){
            return;
        }
        for (SkuLockVo skuLockVo : skuLockVoList) {
            int row = skuStockMapper.minus(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
            if(row==0){
                redisTemplate.delete(key);
                throw new ServiceException("减库存失败");
            }
        }

        //解锁后立即删除缓存。避免重复解锁库存或减库存
        redisTemplate.delete(dataKey);
    }
}